﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace RayTracer
{

    public partial class Window : Form
    {
        int RecursiveDepth = 2;
        int selectedTab = 0;
        bool SceneLoaded = false;
        Data d;
        Graphics g;
        Bitmap b;
        Color c = new Color();
        public Window()
        {
            InitializeComponent();
        }

        private void btLoadScene_Click(object sender, EventArgs e)
        {
            this.d = new Data();
            bool result = this.d.LoadScene("..\\..\\Scene - Transformed.txt");
            if (result)
            {
                status.Text = "Scene Loaded...";

                nudCameraDistance.Value = (int)this.d.l_cameras[0].Distance;
                nudCameraFOV.Value = (int)this.d.l_cameras[0].Fov;

                nudImageHorizontalResolution.Value = this.d.l_images[0].HorizontalResolution;
                nudImageVerticalResolution.Value = this.d.l_images[0].VerticalResolution;

                nudRendererRecursiveDepth.Value = this.RecursiveDepth;

                this.SceneLoaded = true;
            }
        }

        private void btSaveScene_Click(object sender, EventArgs e)
        {
            this.RecursiveDepth = (int)nudRendererRecursiveDepth.Value;
            this.d.l_images[0].HorizontalResolution = (int)nudImageHorizontalResolution.Value;
            this.d.l_images[0].VerticalResolution = (int)nudImageVerticalResolution.Value;
            this.d.l_cameras[0].Distance = (int)nudCameraDistance.Value;
            this.d.l_cameras[0].Fov = (int)nudCameraFOV.Value;
        }

        private void btStartRendering_Click(object sender, EventArgs e)
        {
            if (SceneLoaded)
            {
                if (selectedTab == 0)
                {
                    bw.RunWorkerAsync();
                }
                else
                    status.Text = "Scene not loaded yet...";
            }
        }

        private void btSaveImage_Click(object sender, EventArgs e)
        {
            SaveFileDialog file = new SaveFileDialog();
            file.Filter = "Bitmap (*.bmp)|*.bmp";
            if (file.ShowDialog() == DialogResult.OK)
            {
                b.Save(file.FileName, System.Drawing.Imaging.ImageFormat.Bmp);
            }
        }

        private void btExit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private Triangle intersect_closer(Ray r, ref Point3D p)
        {
            //Console.WriteLine("Intersect test (closer)");
            double taux, t;
            t = 1000000;
            Triangle tri = null;
            foreach (Triangle tr in d.l_triangles)
            {
                taux = intersect(tr.FirstVertex, tr.SecondVertex, tr.ThirdVertex, r.Direction, r.Origin);
                //Console.WriteLine("Taux: " + taux);
                if (taux > 0)
                    if (taux < t)
                    {
                        //Console.WriteLine(taux);
                        t = taux;
                        tri = tr;
                        p.X = r.Origin.X + t * r.Direction.X;
                        p.Y = r.Origin.Y + t * r.direction.Y;
                        p.Z = r.Origin.Z + t * r.Direction.Z;
                    }
            }
            return tri;
        }

        private bool intersect_shadow(Ray r, Vector L)
        {
            double taux;

            foreach (Triangle tr in d.l_triangles)
            {
                taux = intersect(tr.FirstVertex, tr.SecondVertex, tr.ThirdVertex, r.Direction, r.Origin);
                if (taux > 0)
                    if (taux < L.Length)
                    {
                        return true;
                    }
            }
            return false;
        }

        private Color TraceRay(Ray r, int recdepth)
        {
            Point3D p = new Point3D();
            //Console.WriteLine("Trace Ray");

            Triangle tri = intersect_closer(r, ref p);

            
            double c1 = 0.0;

            if (tri == null)
            {
                return this.d.l_images[0].BackgroundColor;
            }
            else
            {
                //ha interseccao
                //return mat.Color;
                Material mat = d.l_materials[tri.Material];
                c = CalcAmbient(mat);

                foreach (Light l in d.l_lights)
                {
                    Vector L_normalized = new Vector();

                    if (!InShadow(p,l,ref L_normalized))
                    { 
                        c += CalcDiffuse(mat, l, L_normalized, tri.CalcNormal());                      
                    }

                    if (recdepth> 0)
                        {
                            //ver coeficiente do material
                            if (mat.Reflection > 0)
                            {
                                c += mat.Color * mat.Reflection * TraceRay(calcReflectedRay(r,tri.CalcNormal(),p, c1),recdepth-1);
                            }
                            if (mat.Refraction > 0)
                            {
                                c1 = -Vector.DotProduct(tri.CalcNormal(), r.Direction);
                                if (c1 >= 0)
                                    c += mat.Color * mat.Refraction * TraceRay(calcRefractedRay(mat,  c1, r.Direction, tri.CalcNormal(), p, true), recdepth - 1);
                                else
                                {
                                    Vector normal = tri.CalcNormal();
                                    Vector normal_inverted = new Vector(-normal.X, -normal.Y, -normal.Z);
                                    c += mat.Color * mat.Refraction * TraceRay(calcRefractedRay(mat,  -c1, r.Direction, normal_inverted, p, false), recdepth - 1);
                                }
                            }
                        }
                }
            }
            return c;
        }


        private Ray calcReflectedRay(Ray v, Vector n, Point3D p, double c1)
        {
            c1 = -Vector.DotProduct(n, v.Direction);
            Ray res = new Ray(p, v.Direction + ((n * 2) * c1));
            return res;
        }


        private Ray calcRefractedRay(Material mat,  double c1, Vector v, Vector normal, Point3D p, bool outside_to_inside)
        {
            double n2 = mat.Refraction_index;
            double n;
            if (outside_to_inside)
                n = 1 / n2;
            else
                n = n2 / 1;
            double c2 = Math.Sqrt(1 - Math.Pow(n, 2) * (1 - Math.Pow(c1, 2)));
            Vector v2 = (v * n) + normal * (n * c1 - c2);
            Ray r = new Ray(p,v2.Normalize());
            return r;
        }



        private Boolean InShadow(Point3D P, Light Q, ref Vector L_normalized)
        {
            Vector L = new Vector(Q.Position.X-P.X, Q.Position.Y-P.Y, Q.Position.Z-P.Z);
            L_normalized = L.Normalize();
            Ray r = new Ray(P, L_normalized);
            return intersect_shadow(r, L);
        }

        private Color CalcDiffuse(Material m, Light l, Vector L_normalized, Vector N_normalized)
        {
            double shade = Vector.DotProduct(L_normalized, N_normalized);
            if (shade > 0)
            {
                return l.Intensity * m.Color * m.Diffuse * shade;
            }
            return new Color(0, 0, 0);
        }

        private Color CalcAmbient(Material m)
        {
            Color c = new Color();
            c = m.Color * m.Ambient;
            return c;
        }

        private double CalcDeterminant(double a11, double a12, double a13, double a21, double a22, double a23, double a31, double a32, double a33)
        {
            return ((a11 * a22 * a33) + (a12 * a23 * a31) + (a13 * a21 * a32) - (a13 * a22 * a31) - (a12 * a21 * a33) - (a11 * a23 * a32));
        }

        private double intersect(Point3D a, Point3D b, Point3D c, Vector d, Point3D r)
        {
            //Console.WriteLine("Intersect Test");
            double detA = CalcDeterminant(a.X - b.X, a.X - c.X, d.X, a.Y - b.Y, a.Y - c.Y, d.Y, a.Z - b.Z, a.Z - c.Z, d.Z);
            double beta, gamma, t;
            beta = CalcDeterminant(a.X - r.X, a.X - c.X, d.X, a.Y - r.Y, a.Y - c.Y, d.Y, a.Z - r.Z, a.Z - c.Z, d.Z) / detA;
            gamma = CalcDeterminant(a.X - b.X, a.X - r.X, d.X, a.Y - b.Y, a.Y - r.Y, d.Y, a.Z - b.Z, a.Z - r.Z, d.Z) / detA;
            t = CalcDeterminant(a.X - b.X, a.X - c.X, a.X - r.X, a.Y - b.Y, a.Y - c.Y, a.Y - r.Y, a.Z - b.Z, a.Z - c.Z, a.Z - r.Z) / detA;
            if (beta < 0 || gamma < 0)
            {
                //Console.WriteLine("Beta or gamma < 0");
                return -1;
            }
            else
                if (beta + gamma < 1)
                {
                    //Console.WriteLine("beta + gamma < 1");
                    if (t >= 0.000001)
                    {
                        //Console.WriteLine("Intersected");
                        return t;
                    }
                }
            return -1;
        }

        private void UpdateSelectedTab(object sender, EventArgs e)
        {
            this.selectedTab = tabControl1.SelectedIndex;
        }

        private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            double height = 2 * this.d.l_cameras[0].Distance * Math.Tan((this.d.l_cameras[0].Fov / 2) * (Math.PI / 180));
            double width = height * ((double)this.d.l_images[0].HorizontalResolution / (double)this.d.l_images[0].VerticalResolution);
            double pixelsize = width / this.d.l_images[0].HorizontalResolution;

            g = panelLocal.CreateGraphics();
            b = new Bitmap(this.d.l_images[0].HorizontalResolution, this.d.l_images[0].VerticalResolution);

            for (int i = 0; i < this.d.l_images[0].HorizontalResolution; i++)
            {
                for (int j = 0; j < this.d.l_images[0].VerticalResolution; j++)
                {
                    double Px = (i + 0.5) * pixelsize - (width / 2);
                    double Py = (j + 0.5) * pixelsize - (height / 2);
                    double Pz = 0.0;
                    Vector direction = new Vector(Px - this.d.l_cameras[0].GetPosition().X, Py - this.d.l_cameras[0].GetPosition().Y, Pz - this.d.l_cameras[0].GetPosition().Z);

                    Vector normalized = direction.Normalize();
                    Point3D origin = this.d.l_cameras[0].GetPosition();

                    Ray ray = new Ray(origin, normalized);

                    Color c = TraceRay(ray, this.RecursiveDepth);

                    if (c.Red > 1.0) c.Red = 1;
                    if (c.Green > 1.0) c.Green = 1;
                    if (c.Blue > 1.0) c.Blue = 1;

                    b.SetPixel(i, j, System.Drawing.Color.FromArgb((int)(c.Red * 255), (int)(c.Green * 255), (int)(c.Blue * 255)));

                }
                bw.ReportProgress((int)((100.0 / this.d.l_images[0].HorizontalResolution * i)+1));
            }
            b.RotateFlip(RotateFlipType.RotateNoneFlipY);
            g.DrawImage(b, 0, 0);
            status.Text = "Image rendered...";
            bw.ReportProgress(0);
        }

        private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            pb.Value = e.ProgressPercentage;
        }
    }
}
